package com.bjpowernode.mvc.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.bjpowernode.common.client.RedisClient;
import com.bjpowernode.common.constant.AppConstants;
import com.bjpowernode.common.constant.RedisKey;
import com.bjpowernode.common.enums.RespCode;
import com.bjpowernode.common.util.NumberUtils;
import com.bjpowernode.db.domain.*;
import com.bjpowernode.db.mapper.*;
import com.bjpowernode.db.result.BidResult;
import com.bjpowernode.mvc.model.dto.BidRankDTO;
import com.bjpowernode.mvc.service.inter.BidService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
import java.util.Set;

@RequiredArgsConstructor
@Service
public class BidServiceImpl implements BidService {

    private final BidInfoMapper bidInfoMapper;
    private final SysAccountMapper sysAccountMapper;
    private final ProductInfoMapper productInfoMapper;
    private final AccountDetailMapper accountDetailMapper;
    private final SysUserMapper sysUserMapper;

    private final RedisClient redisClient;

    /**
     * 获取投资排行榜数据,从Redis中获取
     *
     * @return
     */
    @Override
    public List<BidRankDTO> queryMoneyRank() {
        //包含起始位置 0,1,2
        Set<ZSetOperations.TypedTuple<String>> typedTuples = redisClient.reserveScore(RedisKey.BID_RANK_LIST, 0, 2);

        return typedTuples.stream().map(tuple ->
                new BidRankDTO(tuple.getValue(), tuple.getScore())
        ).toList();
    }

    /**
     * 查询某个产品最近投资记录
     *
     * @param productId
     * @param pageNo    当前第几页
     * @param pageSize  每页多少条
     * @return
     */
    @Override
    public List<BidResult> queryProductId(Integer productId, Integer pageNo, Integer pageSize) {
        //offset  分页的起始位置
        int offset = (pageNo - 1) * pageSize;
        return bidInfoMapper.selectBidByProductId(productId, offset, pageSize);
    }

    /**
     * 投资   购买理财产品
     *
     * @param productId
     * @param bidMoney
     * @return
     * @Transactional(rollbackFor = Exception.class)
     * 事务处理：当遇到rollbackFor指定的异常出现时，就触发回滚
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public synchronized String buyProduct(Integer uid, Integer productId, BigDecimal bidMoney) {


        //1、查询账户余额
        //资源上锁：使用MYSQL的排他锁对查询的数据上锁，只有当当前事务提交或回滚后其他事务才能使用这条数据(删除、更新)
        //在上锁期间其他事务只能查询
        SysAccount sysAccount = sysAccountMapper.selectByUidForUpdate(uid);

        //2、判断资金账户是否有效
        if (sysAccount == null) {
            return RespCode.ACCOUNT_NOT_EXISTS.name();
        }

        //3、判断账户余额是否小于投资金额
        if (NumberUtils.lt(sysAccount.getAvailableMoney(), bidMoney)) {
            //账户余额小于投资金额
            return RespCode.USER_ACCOUNT_ENOUGH.name();
        }

        //4、查询理财产品
        //资源上锁：使用MYSQL的排他锁对查询的数据上锁，只有当当前事务提交或回滚后其他事务才能使用这条数据(删除、更新)
        //在上锁期间其他事务只能查询
        ProductInfo productInfo = productInfoMapper.selectByIdForUpdate(productId);
        if (productInfo == null) {
            //理财产品不存在
            return RespCode.PRODUCT_NOT_EXISTS.name();
        }

        //5、判断剩余可投资金额是否小于投资金额
        if (NumberUtils.lt(productInfo.getLeftProductMoney(), bidMoney)) {
            //理财产品可投资金额不足
            return RespCode.PRODUCT_LEFTMONEY_NOT_ENOUGH.name();
        }

        //6、判断投资金额是否大于最高投资金额
        if (NumberUtils.gt(bidMoney, productInfo.getBidMaxLimit())) {
            //投资金额不在购买范围内
            return RespCode.PRODUCT_BUY_NOT_RANGE.name();
        }

        //7、判断投资金额是否小于最低投资金额
        if (NumberUtils.lt(bidMoney, productInfo.getBidMinLimit())) {
            //投资金额不在购买范围内
            return RespCode.PRODUCT_BUY_NOT_RANGE.name();
        }

        //8、扣除用户的账号余额
        int rows = sysAccountMapper.updateByBidProduct(uid, bidMoney);
        if (rows < 1) {
            //更新失败,抛出异常后，后面的代码块不会继续执行，程序的控制权将转移到全局异常处理类中进行异常处理
            throw new RuntimeException("投资理财产品，扣除账户余额失败！");
        }

        //9、记录资金流水
        AccountDetail accountDetail = new AccountDetail();
        accountDetail.setUid(uid);
        accountDetail.setAction(AppConstants.OPERATION_SUB);
        accountDetail.setActionTime(new Date());
        accountDetail.setMoney(bidMoney);
        accountDetail.setMemo("购买理财产品" + productInfo.getProductName() + ",投资金额为：" + bidMoney);
        int insert = accountDetailMapper.insert(accountDetail);
        if (insert < 1) {
            //记录资金流水失败
            throw new RuntimeException("投资理财产品，记录资金流水失败！");
        }

        //10、扣除理财产品剩余可投资金额
        rows = productInfoMapper.updateLeftMoneyByBidProduct(productId, bidMoney);
        if (rows < 1) {
            //更新失败,抛出异常后，后面的代码块不会继续执行，程序的控制权将转移到全局异常处理类中进行异常处理
            throw new RuntimeException("投资理财产品，扣除理财产品剩余可投资金额失败！");
        }

        //11、生成产品投资记录
        BidInfo bidInfo = new BidInfo();
        bidInfo.setProdId(productId);
        bidInfo.setUid(uid);
        bidInfo.setBidMoney(bidMoney);
        bidInfo.setBidTime(new Date());
        bidInfo.setBidStatus(AppConstants.STA_BID_SUCC);
        insert = bidInfoMapper.insert(bidInfo);
        if (insert < 1) {
            //生成投资记录失败,抛出异常后，后面的代码块不会继续执行，程序的控制权将转移到全局异常处理类中进行异常处理
            throw new RuntimeException("生成投资记录失败！");
        }


        //12、判断理财产品是否满标
        ProductInfo query = productInfoMapper.selectById(productId);
        if (query.getLeftProductMoney().compareTo(new BigDecimal("0")) == 0) {
            //已满标，更新理财产品状态为满标，更新满标时间
            UpdateWrapper<ProductInfo> updateWrapper = new UpdateWrapper<>();
            updateWrapper.eq("id", productId);
            //selled：已售出
            updateWrapper.set("product_status", AppConstants.PRODUCT_STA_SELLED);
            updateWrapper.set("product_full_time", new Date());
            rows = productInfoMapper.update(null, updateWrapper);
        }

        //更新产品满标状态失败
        if (rows < 1) {
            throw new RuntimeException("投资产品，更新产品满标状态失败!");
        }

        //13、更新投资排行榜
        SysUser sysUser = sysUserMapper.selectById(uid);
        redisClient.updateScore(RedisKey.BID_RANK_LIST, sysUser.getPhone(), bidMoney.doubleValue());

        //用户购买理财产品成功，返回成功信息
        return RespCode.OK.name();
    }

    /**
     * 查询最近投资信息
     *
     * @param uid
     * @param pageNo
     * @param pageSize
     * @return
     */
    @Override
    public List<BidResult> queryBidByUid(Integer uid, Integer pageNo, Integer pageSize) {

        //offset  分页的起始位置
        int offset = (pageNo - 1) * pageSize;
        return bidInfoMapper.selectBidByUid(uid, offset, pageSize);
    }
}
